/* Name: Game
 * Author: John Gideon
 * Date: 5-1-12
 * Description: Main controller of the game. Moves thorugh the priority queues and calls the functions
 *  of all Items in the game. Also contains a variety of utility functions.
 * Revisions: See Subversion logs at http://code.google.com/p/software-engineering-2012/source/list
 */

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.util.LinkedList;
import java.util.Random;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Game extends Canvas {
	final private static int WINDOW_WIDTH = 1024;
	final private static int WINDOW_HEIGHT = 768;
	final public static int ROOM_START = 0;
	final public static int ROOM_HELP = 1;
	final public static int ROOM_GAME = 2;
	public static int currentRoom;
	final public static int DIFFICULTY_EASY = 0;
	final public static int DIFFICULTY_MEDIUM = 1;
	final public static int DIFFICULTY_HARD = 2;
	public static int difficulty = 1;
	final public static int SIZE_SMALL = 0;
	final public static int SIZE_MEDIUM = 1;
	final public static int SIZE_LARGE = 2;
	public static int roomSize = 1;
	public static Boolean isPaused = false;
	public static Boolean changingRooms = false;
	
	// GUI Objects
	private static JFrame container;
	private static JPanel panel;
	private static Canvas canvas;
	private static BufferStrategy strategy;
	private static Graphics2D gh;
	private static Boolean toggleFullscreenFlag = false;
	public static int screenWidth = 800;
	public static int screenHeight = 600;
	private static Boolean isFullsceen = true;
	// Frame Rate
	private static long lastLoopTime = 0;
	private static float fps = 60;
	private static int ticks = 0;
	private static long nextGameTime;
	// Event Queue
	private static PriorityQueue pq[];
	public static LinkedList<Item> deleteList = new LinkedList<Item>();
	public static LinkedList<Event> createList = new LinkedList<Event>();
	private static Boolean endRoomFlag = false;
	private static int nextRoom;
	private static Boolean inGame;
	// Random Generator
	private static Random randomGenerator = new Random();
	// Log File
	private static LogFile logFile;
	
	// MAIN: Function is called first when the game starts
	public static void main(String[] args) {
		initGame();
	}
	
	// Creates the PriorityQueues and LogFile. Initializes Planet names, Sound, GUI, and game loop.
	public static void initGame() {
		// Check if windows
		String location;
		if (System.getProperty("os.name").startsWith("Windows")) {
			location = "C:/tmp/SpaceConqueror-log.txt";
		} else { // Linux ?
			location = "/tmp/SpaceConqueror-log.txt";
		}
		
		logFile = new LogFile(location);
		logFile.append("Game Started");
		initGui();
		pq = new PriorityQueue[2];
		pq[0] = new PriorityQueue();
		pq[1] = new PriorityQueue();
		Planet.initNames();
		Sound.init();
		canvas.addMouseListener((MouseListener) new Mouse(canvas));
		canvas.addKeyListener(new KeyInputHandler());
		//Planet.initNames();
		gameStart();
		lastLoopTime = System.nanoTime();
		nextGameTime = lastLoopTime / 1000000;
		while (true) {
			gameLoop();
		}
	} 
	
	// Toggles the state of the game between windowed mode and fullscreen
	private static void toggleFullscreen() { //http://stackoverflow.com/questions/2445971/how-come-jframe-window-size-in-java-does-not-produce-the-size-of-window-specifie
		container.setVisible(false);
		if (isFullsceen) { // Switch to windowed mode
			container.dispose();
			container.setUndecorated(false);
			container.setExtendedState(Frame.NORMAL);
			container.setSize(getOffsets(WINDOW_WIDTH, WINDOW_HEIGHT));
			screenWidth = WINDOW_WIDTH;
			screenHeight = WINDOW_HEIGHT;
			panel.setPreferredSize(getOffsets(screenWidth,screenHeight));
		} else { // Switch to fullscreen mode
			container.dispose();
			container.setUndecorated(true);
			container.setExtendedState(Frame.MAXIMIZED_BOTH);
			Toolkit toolkit = Toolkit.getDefaultToolkit();
			Dimension dimension = toolkit.getScreenSize();
			container.setSize(dimension);
			screenWidth = dimension.width;
			screenHeight = dimension.height;
			panel.setPreferredSize(new Dimension(screenWidth,screenHeight));
		}
		canvas.setBounds(0,0,screenWidth,screenHeight);
		container.setVisible(true);
		if (!inGame) {
			StarField.init(false);
		}
		isFullsceen = !isFullsceen;
		toggleFullscreenFlag = false;
	}
	
	// Calculates the proper dimensions for the given window (to fix the issue with window decorations)
	private static Dimension getOffsets(int width, int height) {
		container.setVisible(true);
		int newWidth = width + container.getInsets().left + container.getInsets().right;
		int newHeight = height + container.getInsets().top + container.getInsets().bottom;
		container.setVisible(false);
		return new Dimension(newWidth, newHeight);
	}
	
	// Sets a flag to toggle fullscreen to fix asynchronous issues
	public static void triggerToggleFullscreenFlag() {
		toggleFullscreenFlag = true;
	}

	// Initialize the GUI display
	private static void initGui() {
		// http://www.cokeandcode.com/info/tut2d.html
		// create a frame to contain our game
		container = new JFrame("Space Conqueror");
		container.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		container.setUndecorated(true);
		container.setExtendedState(Frame.MAXIMIZED_BOTH);
		Toolkit toolkit = Toolkit.getDefaultToolkit();
		Dimension dimension = toolkit.getScreenSize();
		container.setSize(dimension);
		screenWidth = dimension.width;
		screenHeight = dimension.height;
				
		// get hold the content of the frame and set up the 
		// resolution of the game
		panel = (JPanel) container.getContentPane();
		panel.setPreferredSize(new Dimension(screenWidth,screenHeight));
		panel.setLayout(null);
				
		// setup our canvas size and put it into the content of the frame
		canvas = new Canvas();
		canvas.setBounds(0,0,screenWidth,screenHeight);
		panel.add(canvas);
		// Tell AWT not to bother repainting our canvas since we're
		// going to do that our self in accelerated mode
		canvas.setIgnoreRepaint(true);
		
		// finally make the window visible 
		container.pack();
		container.setResizable(false);
		container.setVisible(true);
		
		// create the buffering strategy which will allow AWT
		// to manage our accelerated graphics
		canvas.createBufferStrategy(2);
		strategy = canvas.getBufferStrategy();
	}
	
	// Go to the start screen, initialize the StarField, and start playing music
	private static void gameStart() {
		gotoRoom(ROOM_START);
		StarField.init(false);
		Sound.playMusic(Sound.MUSIC_TITLE);
	}
	
	// This is the main game loop that repeats indefinitely until the game ends - updates and draws everything
	private static void gameLoop() {	
		// Get hold of a graphics context for the accelerated 
		// surface and blank it out
		gh = (Graphics2D) strategy.getDrawGraphics();
		gh.setColor(Color.black);
		gh.fillRect(0,0,screenWidth,screenHeight);
		
		// All Updating Occurs Here
		update();
		draw();
		 
		// finally, we've completed drawing so clear up the graphics
		// and flip the buffer over
		gh.dispose();		
		strategy.show();
		
		// Determine Delay and FPS
		final int frameRate = 60;
		final int frameTime = 1000 / frameRate;
		long curTimeNs = System.nanoTime();
		int curTime = (int) (curTimeNs/ 1000000);
		if (++ticks > frameRate/2) {
			ticks = 0;
			long delta = curTimeNs - lastLoopTime;
			lastLoopTime = curTimeNs;
			if (delta != 0) {
				fps = ((frameRate/2)*1000000000.0f/(float)delta);
			}
			if (fps > 65) {
				nextGameTime = curTime;
			}
		}
		nextGameTime += frameTime;
		int sleepTime = (int) (nextGameTime - curTime);
		if (sleepTime >= 0) {
			try { Thread.sleep(sleepTime); } catch (Exception e) {}
		} else {
			// Running Behind
		}
		
		if (toggleFullscreenFlag) {
			toggleFullscreen();
		}
	}
	
	// Change room if necessary, refresh mouse state, create Items, update Items, perform second pulses, and delete Items 
	private static void update() {	
		// Change the room if necessary
		if (endRoomFlag) { 
			changingRooms = true;
			while (!pq[0].isEmpty()) {
				pq[0].pop().getItem().destroy();
			}
			switch (nextRoom) {
				case ROOM_START:
					currentRoom = ROOM_START;
					inGame = false;
					new StartScreen();
					ViewControl.init(false);
					break;
				case ROOM_HELP:
					currentRoom = ROOM_HELP;
					inGame = false;
					new HelpScreen();
					ViewControl.init(false);
					break;
				case ROOM_GAME:
					currentRoom = ROOM_GAME;
					inGame = true;
					new GameScreen();
					ViewControl.init(true);
					StarField.init(true);
					break;				
			}
			endRoomFlag = false;
			changingRooms = false;
		}
		// Refresh Mouse State
		Mouse.updateLocation();
		ViewControl.update();
		Boolean triggerLeftPress = Mouse.getLeftPressed();
		Boolean triggerRightPress = Mouse.getRightPressed();
		Boolean triggerLeftRelease = Mouse.getLeftReleased();
		Boolean triggerRightRelease = Mouse.getRightReleased();
		Boolean secondPassed = GameScreen.secondPassed;
		// Creates
		while (!createList.isEmpty()) {
			pq[0].push(createList.pop());
		}
		// Updates
		while (!pq[0].isEmpty()) {
			Event tmpEvent = pq[0].pop();
			Item tmpItem = tmpEvent.getItem();
			if (!isPaused || tmpItem instanceof PauseBtn || tmpItem instanceof CloseBtn || tmpItem instanceof MuteBtn) {
				tmpItem.update();
				if (!(Mouse.getY() < 50 || Mouse.getY() > Game.screenHeight-50) || (tmpItem instanceof Button && !(tmpItem instanceof Planet))) {
					if (triggerLeftPress) {tmpEvent.getItem().leftMousePress();}
					if (triggerRightPress) {tmpEvent.getItem().rightMousePress();}
					if (triggerLeftRelease) {tmpEvent.getItem().leftMouseRelease();}
					if (triggerRightRelease) {tmpEvent.getItem().rightMouseRelease();}
				}
			}
			pq[1].addToFirst(tmpEvent);
		}
		if (!isPaused) {
			Planet.SetPlanetActivity();
			if(secondPassed){
				GameScreen.secondPassed = false;
				Wormhole.secondPulse();
			    Planet earth = GameScreen.getEarth();
			    Nebula.secondPulse();
			    AsteroidField.secondPulse();
			    earth.secondPulse();
			}
		}
		while (!deleteList.isEmpty() && !endRoomFlag) {
			Item nextDelete = deleteList.pop();
			if (nextDelete != null) {
				nextDelete.destroy();
				pq[1].removeItem(nextDelete);
			}
		}
	}

	// Draw all objects on the PriorityQueue
	private static void draw() {
		StarField.draw();
		ViewControl.startView();
		while (!pq[1].isEmpty()) {
			Event tmpEvent = pq[1].popBack();
			tmpEvent.getItem().draw();
			pq[0].addToLast(tmpEvent);
		}
		ViewControl.stopView();
		gh.setFont(new Font("Serif", Font.PLAIN, 20));
		gh.setColor(Color.BLUE);
		String fpsStr = String.format("FPS: %.0f", Game.getFps());
		gh.drawChars(fpsStr.toCharArray(), 0, fpsStr.length(), 5, 30);
	}
	
	// Return the graphics handle for drawing
	public static Graphics2D getGraphicsHandle() {
		return gh;
	}
	
	// Add an Item to the creation list
	public static void create(Object ob, int priority) {
		// WARNING: Cannot be called during draw event
		Event newEvent = new Event(priority, (Item)ob);
		createList.push(newEvent);
	}

	// Add an Item to the deletion list
	public static void delete(Object ob) {
		// WARNING: Cannot be called during draw event
		// make sure has not already been deleted 
		if (!deleteList.contains((Item)ob)) {
			deleteList.push((Item)ob);
		}
	}
	
	// Set a flag for ending the current room and moving to another
	public static void gotoRoom(int pNextRoom) {
		endRoomFlag = true;
		if (pNextRoom == ROOM_START) {
			StarField.init(false);
		}
		nextRoom = pNextRoom;
	}
	
	// Return a random integer
	public static int randomInt(int max) {
		return randomGenerator.nextInt(max);
	}
	public static int randomInt(int min, int max) {
		return min + randomGenerator.nextInt(max-min);
	}
	
	// Return a random float
	public static float randomFloat() {
		return randomGenerator.nextFloat();
	}
	public static float randomFloat(float max) {
		return randomGenerator.nextFloat()*max;
	}
	public static float randomFloat(float min, float max) {
		return min + randomGenerator.nextFloat()*(max-min);
	}
	
	// Return the frames per second count
	public static float getFps() {return fps;}
	
	// End the game and write to the log
	public static void quitGame() {
		logFile.append("Game Ended");
		System.exit(0);
	}
	
	// Add a new entry to the log
	public static void appendLog(String val) {
		logFile.append(val);
	}
}